﻿Public Class frmTrain
    Private TrainsIndexAssociation As New Dictionary(Of Integer, Integer)
    Private CurrentTrain As Integer = -1
    Private PrevSelectedCbDepart As Integer = -1
    Private PrevSelectedCbArrivee As Integer = -1

    Private Sub rbParcoursAuto_CheckedChanged(sender As Object, e As EventArgs) Handles rbParcoursAuto.CheckedChanged
        ' Cache les composants pour le parcours
        lblDepart.Visible = False
        cbDepart.Visible = False
        lblArrivee.Visible = False
        cbArrivee.Visible = False
        gbStations.Visible = False
        clbStations.Visible = False
        gbTrains.Height = 130
    End Sub

    Private Sub rbParcoursAuto_Click(sender As Object, e As EventArgs) Handles rbParcoursAuto.Click
        ' Met à jour les infos sur le parcours lors d'un clic
        frmMicheline.Simulation.Trains.Item(CurrentTrain).Route.Clear()
        dgvTrains.Item(2, dgvTrains.SelectedRows.Item(0).Index).Value = "Automatique"
    End Sub

    Private Sub rbParcours_CheckedChanged(sender As Object, e As EventArgs) Handles rbParcours.CheckedChanged
        ' Affiche les composants pour le parcours
        lblDepart.Visible = True
        cbDepart.Visible = True
        lblArrivee.Visible = True
        cbArrivee.Visible = True
        gbStations.Visible = True
        clbStations.Visible = True
        gbTrains.Height = 274

        ' RAZ interface
        clbStations.Items.Clear()
        cbDepart.SelectedIndex = -1
        cbArrivee.SelectedIndex = -1
    End Sub

    Public Sub RefreshStations()
        ' Nettoie les listes relatives aux stations
        cbDepart.Items.Clear()
        cbArrivee.Items.Clear()

        Dim I As Integer = 0
        Dim Ok As Boolean

        ' Pour chaque station de la simulation
        For Each kvp As KeyValuePair(Of Integer, Station) In frmMicheline.Simulation.Stations
            Ok = True

            ' On vérifie que la station n'est pas une station d'aiguillage
            For Each RailPoint In frmMicheline.Simulation.RailPoints
                If RailPoint.MiddleStationIndex = kvp.Key Then Ok = False
            Next

            If Ok Then
                ' Ajoute le nom de la station aux deux combobox
                cbDepart.Items.Add(kvp)
                cbArrivee.Items.Add(kvp)
                ' On passe à la station suivante
                I = I + 1
            End If
        Next

        ' On veut afficher le nom de la station et pas la KeyValuePair
        cbDepart.DisplayMember = "Value"
        cbArrivee.DisplayMember = "Value"
    End Sub

    Private Sub RefreshTrains()
        ' Ancienne ligne sélectionnée dans la grid view
        Dim prevSelected As Integer = -1

        ' Si un train est en cours d'édition et qu'il y au moins une ligne dans la grid view
        If CurrentTrain > -1 And dgvTrains.RowCount > 0 Then
            ' Si on vient de supprimer une ligne
            If dgvTrains.RowCount > frmMicheline.Simulation.Trains.Count Then
                ' On décale la sélection vers le haut
                prevSelected = dgvTrains.SelectedRows.Item(0).Index - 1
            Else
                ' Sinon on garde la même ligne
                prevSelected = dgvTrains.SelectedRows.Item(0).Index
            End If
        End If

        ' Efface la grid view ainsi que la dictionnaire d'association Indice Train <> Num Ligne
        dgvTrains.Rows.Clear()
        TrainsIndexAssociation.Clear()

        ' Variable d'incrémentation du numéro de la ligne
        Dim I As Integer = 0

        ' Pour chaque train de la simulation
        For Each kvp As KeyValuePair(Of Integer, Train) In frmMicheline.Simulation.Trains
            ' Ajoute la clé au dico d'association
            TrainsIndexAssociation.Add(I, kvp.Key)
            ' Ajoute une nouvelle ligne dans la liste des trains
            dgvTrains.Rows.Add()

            ' Assigne le numéro du train ou son nom (si la numérotation auto est désactivée)
            If cbAutoNum.Checked Then
                dgvTrains.Item(0, I).Value = kvp.Key
            Else
                dgvTrains.Item(0, I).Value = kvp.Value.Name
            End If

            ' Nombre de wagons
            dgvTrains.Item(1, I).Value = kvp.Value.WagonCount

            ' Parcours
            If kvp.Value.Route.IsActive Then
                dgvTrains.Item(2, I).Value = frmMicheline.Simulation.Stations.Item(kvp.Value.Route.Stations.Last).Name & " - " & _
                                             frmMicheline.Simulation.Stations.Item(kvp.Value.Route.Stations.First).Name
            Else
                dgvTrains.Item(2, I).Value = "Automatique"
            End If

            ' On passe à la ligne suivante
            I = I + 1
        Next

        ' Si on a sauvegardé la ligne sélectionnée, on la restaure
        If prevSelected > -1 And dgvTrains.RowCount > 0 Then dgvTrains.Rows(prevSelected).Selected = True
    End Sub

    Private Sub frmTrain_Load(sender As Object, e As EventArgs) Handles MyBase.Load
        ' Génère la liste des stations
        RefreshStations()
        RefreshTrains()
        CheckNoTrain()

        ' Si il y a au moins un train dans la simulation
        If TrainsIndexAssociation.Count > 0 Then
            CurrentTrain = TrainsIndexAssociation.Item(0)
        End If
    End Sub

    Private Sub CheckNoTrain()
        ' Si il n'y a pas de trains dans la liste, on réduit la taille de la fenêtre (on cache la groupbox d'édition du train)
        If (dgvTrains.RowCount = 0) Then
            Me.Width = 387
            gbTrains.Visible = False
        Else
            Me.Width = 620
            gbTrains.Visible = True
        End If
    End Sub

    Private Sub SelectionIndexChanged(sender As Object, e As EventArgs) Handles cbArrivee.SelectedIndexChanged, cbDepart.SelectedIndexChanged
        ' Si les stations sont identiques
        If cbDepart.SelectedIndex = cbArrivee.SelectedIndex And cbDepart.SelectedIndex > -1 And cbArrivee.SelectedIndex > -1 Then
            MessageBox.Show("Stations de départ et d'arrivée identiques !", "Projet Micheline", MessageBoxButtons.OK, MessageBoxIcon.Exclamation)
            ' On restaure les anciennes stations
            cbArrivee.SelectedIndex = PrevSelectedCbArrivee
            cbDepart.SelectedIndex = PrevSelectedCbDepart
        Else
            ' Si elles sont différentes
            If cbDepart.SelectedIndex > -1 And cbArrivee.SelectedIndex > -1 Then Dijkstra()
            ' On les mémorise afin si on doit les restaurer par la suite
            PrevSelectedCbArrivee = cbArrivee.SelectedIndex
            PrevSelectedCbDepart = cbDepart.SelectedIndex
        End If
    End Sub

    Private Sub cbAutoNum_CheckStateChanged(sender As Object, e As EventArgs) Handles cbAutoNum.CheckStateChanged
        ' Met à jour la liste de trains
        RefreshTrains()

        ' Si une ligne dans la liste est sélectionnée
        If dgvTrains.SelectedRows.Count > 0 Then
            ' Met à jour la groupbox de droite
            tbNomTrain.Text = dgvTrains.Item(dgvTrains.CurrentRow.Index, 0).Value
            tbNomTrain_Disabling()
        End If
    End Sub

    Private Sub tbNomTrain_Disabling()
        ' Si la numérotation automatique est activée, ne pas activer la textbox représentant le nom du train
        If cbAutoNum.Checked Then
            tbNomTrain.Enabled = False
        Else
            tbNomTrain.Enabled = True
        End If
    End Sub

    Private Sub ShowTrain()
        ' Si on est en mode numérotation automatique on affiche l'indice du train et pas son nom
        tbNomTrain.Text = If(cbAutoNum.Checked, CurrentTrain, frmMicheline.Simulation.Trains.Item(CurrentTrain).Name)
        nudNbWagons.Value = frmMicheline.Simulation.Trains.Item(CurrentTrain).WagonCount
        tbNomTrain_Disabling()

        ' Si le train en cours possède un parcours défini
        If frmMicheline.Simulation.Trains.Item(CurrentTrain).Route.IsActive() Then
            ' On coche le mode parcours
            rbParcours.Checked = True

            ' On met à jour les stations
            RefreshStations()

            Dim Stops As New List(Of Integer)(frmMicheline.Simulation.Trains.Item(CurrentTrain).Route.Stops)

            ' On met à jour les combo box
            cbDepart.SelectedItem = New KeyValuePair(Of Integer, Station)(frmMicheline.Simulation.Trains.Item(CurrentTrain).Route.Stations.Last, _
                                    frmMicheline.Simulation.Stations.Item(frmMicheline.Simulation.Trains.Item(CurrentTrain).Route.Stations.Last))
            cbArrivee.SelectedItem = New KeyValuePair(Of Integer, Station)(frmMicheline.Simulation.Trains.Item(CurrentTrain).Route.Stations.First, _
                                     frmMicheline.Simulation.Stations.Item(frmMicheline.Simulation.Trains.Item(CurrentTrain).Route.Stations.First))

            ' Puis on ajoute chaque stop à la checked list box
            For Each Stp In Stops
                Dim idx As Integer = clbStations.Items.IndexOf(New KeyValuePair(Of Integer, Station)(Stp, frmMicheline.Simulation.Stations.Item(Stp)))
                clbStations.SetItemCheckState(idx, If(Stp = frmMicheline.Simulation.Trains.Item(CurrentTrain).Route.Stations.First Or _
                                                      Stp = frmMicheline.Simulation.Trains.Item(CurrentTrain).Route.Stations.Last, _
                                                      CheckState.Indeterminate, CheckState.Checked))
            Next
        Else
            ' Sinon, on coche juste le mode parcours auto
            rbParcoursAuto.Checked = True
        End If
    End Sub

    Private Sub tbNomTrain_KeyPress(sender As Object, e As KeyPressEventArgs) Handles tbNomTrain.KeyPress
        ' Si c'est la touche Entrée
        If e.KeyChar = ChrW(Keys.Enter) Then
            ' On valide son nom dans la simulation
            frmMicheline.Simulation.Trains.Item(CurrentTrain).Name = tbNomTrain.Text
            RefreshTrains()
            ' Et on indique que l'évènement a été traité (pour éviter le son d'erreur lancé par Windows)
            e.Handled = True
        End If
    End Sub

    Private Sub nudNbWagons_ValueChanged(sender As Object, e As EventArgs) Handles nudNbWagons.ValueChanged
        ' Si un train est en cours d'édition
        If CurrentTrain > -1 Then
            ' On actualise le nombre de wagons du train en cours dans la simulation
            frmMicheline.Simulation.Trains.Item(CurrentTrain).WagonCount = nudNbWagons.Value
            ' On met à jour son nombre de wagons dans la liste des trains
            dgvTrains.Item(1, dgvTrains.SelectedRows.Item(0).Index).Value = nudNbWagons.Value
            ' Puis on rafraichit l'affichage
            frmMicheline.Simulation.RefreshTrains()
            frmMicheline.pbSimulation.Refresh()
        End If
    End Sub

    Private Sub btnAjouter_Click(sender As Object, e As EventArgs) Handles btnAjouter.Click
        If frmMicheline.Simulation.Stations.Count > 0 Then
            ' Ajout d'un train à la simulation
            frmMicheline.Simulation.AddTrain()
            frmMicheline.Simulation.RefreshTrains()
            frmMicheline.pbSimulation.Refresh()

            ' Si il y au moins un train dans la simulation
            If frmMicheline.Simulation.Trains.Count > 0 Then
                RefreshTrains()
                CheckNoTrain()
                CurrentTrain = TrainsIndexAssociation.Last.Value
                ' On sélectionne la dernière ligne représentant ce train dans notre grid view 
                dgvTrains.Rows(dgvTrains.Rows.GetLastRow(False)).Selected = True
            End If
        Else
            ' Message avertissant l'utilisateur qu'il doit ajouter des stations pour créer un train 
            MessageBox.Show("Créez votre simulation avant d'ajouter un train.", "Avertissement", MessageBoxButtons.OK, MessageBoxIcon.Warning)
        End If
    End Sub

    Private Function GetMin(DM As Dictionary(Of Integer, Double), Marked As Dictionary(Of Integer, Boolean)) As Integer
        ' Index représentant la station la plus proche (-1 = aucune)
        Dim min As Integer = -1

        ' On cherche un DM non marqué
        For Each kvp As KeyValuePair(Of Integer, Boolean) In Marked
            If Not kvp.Value Then min = kvp.Key
        Next

        ' Si il n'y en pas, le signaler à l'algorithme
        If min = -1 Then Return min

        ' Sinon, effectuer le minimum à partir des DM non marqués
        For Each kvp As KeyValuePair(Of Integer, Double) In DM
            If (Not Marked.Item(kvp.Key)) And (kvp.Value < DM.Item(min)) Then
                min = kvp.Key
            End If
        Next

        ' Puis retourner l'index
        Return min
    End Function

    ' Fonction permettant de calculer le plus court chemin entre deux stations saisies par l'utilisateur
    Private Sub Dijkstra()
        ' Récupération des indices des stations sélectionnées dans la combo box
        Dim DepartIndex As Integer = CType(cbDepart.SelectedItem, KeyValuePair(Of Integer, Station)).Key
        Dim ArriveeIndex As Integer = CType(cbArrivee.SelectedItem, KeyValuePair(Of Integer, Station)).Key

        ' Dictionnaire DM représentant la distance de chaque station par rapport à la station de départ
        Dim DM As New Dictionary(Of Integer, Double)
        ' Déclaration d'un dictionnaire des pères associant chaque station à son antécédent le plus proche dans le graphe
        Dim Fathers As New Dictionary(Of Integer, Integer)
        ' Dictionnaire permettant de déterminer si la station a déjà été visité par l'algorithme (fonctionnement interne)
        Dim Marked As New Dictionary(Of Integer, Boolean)

        ' Initialisation Dijkstra
        For Each kvp As KeyValuePair(Of Integer, Station) In frmMicheline.Simulation.Stations
            ' Chaque DM aura comme valeur + infini
            DM.Add(kvp.Key, Double.MaxValue)
            ' On met chaque valeur du tableau des pères à -1 pour indiquer qu'elle n'a pas été visitée
            Fathers.Add(kvp.Key, -1)
            ' Aucune station n'est marquée pour l'instant
            Marked.Add(kvp.Key, False)
        Next

        'Mise à jour de la valeur de la distance entre la station de départ et elle même
        DM.Item(DepartIndex) = 0

        ' Indice de la station non marquée la plus proche de la station en cours d'analyse
        Dim MdistIndex As Integer = GetMin(DM, Marked)

        ' Tant qu'il reste des stations non marquées
        While (MdistIndex <> -1)
            ' On marque la station en cours
            Marked.Item(MdistIndex) = True

            ' On parcourt les successeurs de la station en cours (les voisins directs dans le graphe)
            For Each Successor In GetNeighbors(MdistIndex, frmMicheline.Simulation.Junctions, frmMicheline.Simulation.RailPoints)
                ' Si ce successeur n'est pas marqué
                If Not Marked.Item(Successor) Then
                    ' Si il est plus économique en longueur de parcours
                    If DM.Item(MdistIndex) + GetDistance(frmMicheline.Simulation.Stations.Item(MdistIndex), _
                                                         frmMicheline.Simulation.Stations.Item(Successor)) < DM.Item(Successor) Then
                        ' Alors on met à jour la longueur du parcours jusqu'à ce successeur
                        DM.Item(Successor) = DM.Item(MdistIndex) + GetDistance(frmMicheline.Simulation.Stations.Item(MdistIndex), _
                                                                               frmMicheline.Simulation.Stations.Item(Successor))
                        ' On met à jour le dictionnaire des pères, le chemin le plus court passera par cette station (ce successeur)
                        Fathers.Item(Successor) = MdistIndex
                    End If
                End If
            Next

            ' On recherche la station la plus proche
            MdistIndex = GetMin(DM, Marked)
        End While

        ' Si le trajet est impossible (père non mis à jour)
        If Fathers.Item(ArriveeIndex) = -1 Then
            ' Affiche l'erreur
            MessageBox.Show("Trajet impossible !", "Projet Micheline", MessageBoxButtons.OK, MessageBoxIcon.Exclamation)
            ' Restaure les anciennes stations sélectionnées
            cbArrivee.SelectedIndex = PrevSelectedCbArrivee
            cbDepart.SelectedIndex = PrevSelectedCbDepart
            Exit Sub
        End If

        ' On se prépare à remonter le tableau des pères
        Dim Prec As Integer = ArriveeIndex

        ' Nettoyage de la liste des stations
        clbStations.Items.Clear()
        frmMicheline.Simulation.Trains.Item(CurrentTrain).Route.Clear()

        ' Tant qu'on a pas atteint la station de départ
        Do
            ' On ajoute en haut de la liste l'antécédent de la station (en partant de la fin)
            Insert(Prec, If(Prec = ArriveeIndex Or Prec = DepartIndex, False, True))
            frmMicheline.Simulation.Trains.Item(CurrentTrain).Route.AddStation(Prec)
            Prec = Fathers.Item(Prec)
        Loop Until (Prec = DepartIndex)

        ' La boucle ne remontera pas jusqu'à la station de départ donc il faut la rajouter à la main
        Insert(DepartIndex, If(Prec = ArriveeIndex Or Prec = DepartIndex, False, True))
        frmMicheline.Simulation.Trains.Item(CurrentTrain).Route.AddStation(Prec)

        ' Les stations de départ et d'arrivée sont des arrêts obligatoires
        frmMicheline.Simulation.Trains.Item(CurrentTrain).Route.SetStop(DepartIndex)
        frmMicheline.Simulation.Trains.Item(CurrentTrain).Route.SetStop(ArriveeIndex)

        ' On affiche le nom de la station dans la liste
        clbStations.DisplayMember = "Value"

        ' On positionne le train au début du parcours
        frmMicheline.Simulation.Trains.Item(CurrentTrain).PrevStation = _
            frmMicheline.Simulation.Trains.Item(CurrentTrain).Route.Stations.Item(frmMicheline.Simulation.Trains.Item(CurrentTrain).Route.Stations.Count - 1)
        frmMicheline.Simulation.Trains.Item(CurrentTrain).HeadingTo = _
            frmMicheline.Simulation.Trains.Item(CurrentTrain).Route.Stations.Item(frmMicheline.Simulation.Trains.Item(CurrentTrain).Route.Stations.Count - 2)
        frmMicheline.Simulation.Trains.Item(CurrentTrain).JunctionPosition = 10
        frmMicheline.Simulation.Trains.Item(CurrentTrain).ValidateNextStation(frmMicheline.Simulation.Junctions, frmMicheline.Simulation.RailPoints)
        frmMicheline.Simulation.RefreshTrains()
        frmMicheline.pbSimulation.Refresh()

        ' Puis on met à jour notre liste des trains
        dgvTrains.Item(2, dgvTrains.SelectedRows.Item(0).Index).Value = _
            frmMicheline.Simulation.Stations.Item(frmMicheline.Simulation.Trains.Item(CurrentTrain).Route.Stations.Last).Name & " - " & _
            frmMicheline.Simulation.Stations.Item(frmMicheline.Simulation.Trains.Item(CurrentTrain).Route.Stations.First).Name
    End Sub

    Private Sub Insert(Station As Integer, Enable As Boolean)
        Dim Ok As Boolean = True

        ' On vérifie que la station n'est pas une station d'aiguillage
        For Each RailPoint In frmMicheline.Simulation.RailPoints
            If RailPoint.MiddleStationIndex = Station Then Ok = False
        Next

        If Ok Then
            ' Puis on ajoute la station par le haut de la liste en poussant les autres par le bas
            clbStations.Items.Insert(0, New KeyValuePair(Of Integer, Station)(Station, frmMicheline.Simulation.Stations.Item(Station)))
            clbStations.SetItemCheckState(0, If(Enable, CheckState.Unchecked, CheckState.Indeterminate))
        End If
    End Sub

    Private Sub btnSupprimer_Click(sender As Object, e As EventArgs) Handles btnSupprimer.Click
        ' Si on est un train est en cours d'édition
        If CurrentTrain > -1 Then
            ' On demande confirmation à l'utilisateur
            Dim result = MessageBox.Show("Êtes-vous sûr de vouloir supprimer ce train ?", "Confirmation", MessageBoxButtons.YesNo, MessageBoxIcon.Question)

            ' Si la réponse est positive
            If result = Windows.Forms.DialogResult.Yes Then
                ' On retire le train de la simulation
                frmMicheline.Simulation.Trains.Remove(CurrentTrain)
                ' On actualise notre liste des trains
                RefreshTrains()
                ' On rafraichit le tampon d'affichage
                frmMicheline.Simulation.RefreshTrains()
                ' On demande un rafraichissement de la surface d'affichage de la simulation
                frmMicheline.pbSimulation.Refresh()
                ' Et on effectue un test afin de redimensionner la fenêtre si il n'y a plus de train dans la liste
                CheckNoTrain()
            End If
        End If
    End Sub

    Private Sub dgvTrains_SelectionChanged(sender As Object, e As EventArgs) Handles dgvTrains.SelectionChanged
        ' Si une ligne est sélectionnée
        If dgvTrains.SelectedRows.Count > 0 Then
            ' Le train en cours d'édition sera celui dont l'index est pointé par l'indice de la ligne dans le tableau d'association
            CurrentTrain = TrainsIndexAssociation.Item(dgvTrains.SelectedRows.Item(0).Index)
            ' On l'affiche dans la groupbox de droite
            ShowTrain()
        End If
    End Sub

    Private Sub clbStations_ItemCheck(sender As Object, e As ItemCheckEventArgs) Handles clbStations.ItemCheck
        ' Définit ou non l'arrêt en fonction de si la check box est cochée ou pas
        If e.NewValue = CheckState.Checked Then
            frmMicheline.Simulation.Trains.Item(CurrentTrain).Route.SetStop(CType(clbStations.Items(e.Index), KeyValuePair(Of Integer, Station)).Key)
        Else
            frmMicheline.Simulation.Trains.Item(CurrentTrain).Route.UnsetStop(CType(clbStations.Items(e.Index), KeyValuePair(Of Integer, Station)).Key)
        End If
    End Sub
End Class